{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "博客：<https://www.cnblogs.com/xzwblog/p/6834663.html>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "HTTP请求报头： Authorization 　　　[ˌɔ:θəraɪˈzeɪʃn]\n",
    "\n",
    "HTTP响应报头： WWW-Authenticate 　　　[ɔ:ˈθentɪkeɪt]\n",
    "\n",
    "HTTP认证是基于质询/回应(challenge/response)的认证模式。\n",
    "\n",
    "# HTTP 认证\n",
    "\n",
    "## BASIC 认证\n",
    "\n",
    "### BASIC 认证 概述\n",
    "\n",
    "向一个客户端向HTTP服务器进行数据请求时，如果客户端未被认证，则HTTP服务器将通过基本认证过程对客户端的用户名及密码进行验证，以决定用户是否合法。客户端在接收到HTTP服务器的认证要求后，会提示用户输入用户名及密码，然后将用户名及密码以BASE64加密，加密后的密文附加于请求信息中，**如当用户名为anjuta，密码为：123456时，客户端将用户名和密码用“：”合并，并将合并后的字符串用BASE64加密为密文，并于每次请求数据时，将密文附加请求头（Authorization）中**。HTTP服务器在每次收到请求包后，根据协议取得客户端附加的用户信息（BASE64加密的用户名和密码），解开请求包，对用户名及密码进行验证，如果用户名及密码正确，则根据客户端请求，返回客户端所需要的数据;否则，返回错误代码或重新要求客户端提供用户名及密码。\n",
    "\n",
    "### BASIC 认证 的 过程\n",
    "\n",
    "基本认证步骤：\n",
    "1. 客户端访问一个受http基本认证保护的资源。\n",
    "2. 服务器返回401状态，要求客户端提供用户名和密码进行认证。（验证失败的时候，响应头会加上WWW-Authenticate: Basic realm=\"请求域\"。）\n",
    "如：401 Unauthorized\n",
    "WWW-Authenticate： Basic realm=\"WallyWorld\"\n",
    "3. 客户端将输入的用户名密码用Base64进行编码后，采用非加密的明文方式传送给服务器。\n",
    "Authorization: Basic xxxxxxxxxx.\n",
    "4. 服务器将Authorization头中的用户名密码解码并取出，进行验证，如果认证成功，则返回相应的资源。如果认证失败，则仍返回401状态，要求重新进行认证。\n",
    "\n",
    "\n",
    "### BASIC认证的JAVA实现代码\n",
    "\n",
    "```java\n",
    "     HttpSession session=request.getSession();\n",
    "     String user=(String)session.getAttribute(\"user\");\n",
    "     String pass;\n",
    "     if(user==null){\n",
    "     try{\n",
    "        response.setCharacterEncoding(\"GBK\");\n",
    "        PrintWriter ut=response.getWriter();\n",
    "        String authorization=request.getHeader(\"authorization\");\n",
    "        if(authorization==null||authorization.equals(\"\")){\n",
    "            response.setStatus(401);\n",
    "            response.setHeader(\"WWW-authenticate\",\"Basic realm=\\\"请输入管理员密码\\\"\");\n",
    "            out.print(\"对不起你没有权限！！\");\n",
    "            return;\n",
    "        }\n",
    "    String userAndPass=new String(new BASE64Decoder().decodeBuffer(authorization.split(\" \")[1]));\n",
    "    if(userAndPass.split(\":\").length<2){\n",
    "        response.setStatus(401);\n",
    "        response.setHeader(\"WWW-authenticate\",\"Basic realm=\\\"请输入管理员密码\\\"\");\n",
    "        out.print(\"对不起你没有权限！！\");\n",
    "        return;\n",
    "    }\n",
    "    user=userAndPass.split(\":\")[0];\n",
    "    pass=userAndPass.split(\":\")[1];\n",
    "    if(user.equals(\"111\")&&pass.equals(\"111\")){\n",
    "        session.setAttribute(\"user\",user);\n",
    "        RequestDispatcher dispatcher=request.getRequestDispatcher(\"index.jsp\");\n",
    "        dispatcher.forward(request,response);\n",
    "    }else{\n",
    "        response.setStatus(401);\n",
    "        response.setHeader(\"WWW-authenticate\",\"Basic realm=\\\"请输入管理员密码\\\"\");\n",
    "        out.print(\"对不起你没有权限！！\");\n",
    "        return;\n",
    "    }\n",
    "     }catch(Exception ex){\n",
    "        ex.printStackTrace();\n",
    "     }\n",
    "     }else{\n",
    "        RequestDispatcher dispatcher=request.getRequestDispatcher(\"index.jsp\");\n",
    "        dispatcher.forward(request, response);\n",
    "    }\n",
    "```\n",
    "\n",
    "### 特记事项\n",
    "\n",
    "1. Http是无状态的，同一个客户端对同一个realm内资源的每一个访问会被要求进行认证。\n",
    "\n",
    "2. 客户端通常会缓存用户名和密码，并和authentication realm一起保存，所以，一般不需要你重新输入用户名和密码。\n",
    "\n",
    "3. 以非加密的明文方式传输，虽然转换成了不易被人直接识别的字符串，但是无法防止用户名密码被恶意盗用。虽然用肉眼看不出来，但用程序很容易解密。\n",
    "\n",
    "### 优点\n",
    "\n",
    "基本认证的一个优点是基本上所有流行的网页浏览器都支持基本认证。基本认证很少在可公开访问的互联网网站上使用，有时候会在小的私有系统中使用（如路由器\n",
    "网页管理接口）。后来的机制HTTP摘要认证是为替代基本认证而开发的，允许密钥以相对安全的方式在不安全的通道上传输。\n",
    "\n",
    "### 缺点\n",
    "\n",
    "- 虽然基本认证非常容易实现，但该方案建立在以下的假设的基础上，即：客户端和服务器主机之间的连接是安全可信的。特别是，如果没有使用SSL/TLS这样的传输\n",
    "层安全的协议，那么以明文传输的密钥和口令很容易被拦截。该方案也同样没有对服务器返回的信息提供保护。\n",
    "现存的浏览器保存认证信息直到标签页或浏览器被关闭，或者用户清除历史记录。HTTP没有为服务器提供一种方法指示客户端丢弃这些被缓存的密钥。这意味着服务\n",
    "器端在用户不关闭浏览器的情况下，并没有一种有效的方法来让用户登出。\n",
    "\n",
    "## HTTP OAuth认证\n",
    "\n",
    "OAuth对于Http来说，就是放在Authorization header中的不是用户名密码， 而是一个token。微软的Skydrive就是使用这样的方式。\n",
    "参考：http://www.tuicool.com/articles/qqeuE3\n",
    "\n",
    "### 🔴摘要认证\n",
    "\n",
    "digest authentication（HTTP1.1提出的基本认证的替代方法）\n",
    "这个认证可以看做是基本认证的增强版本，不包含密码的明文传递。\n",
    "引入了一系列安全增强的选项；“保护质量”(qop)、随机数计数器由客户端增加、以及客户生成的随机数。\n",
    "\n",
    "![digisst](../assets/digist.png)\n",
    "\n",
    "在HTTP摘要认证中使用 MD5 加密是为了达成\"不可逆的\"，也就是说，当输出已知的时候，确定原始的输入应该是相当困难的。如果密码本身太过简单，也许可以\n",
    "通过尝试所有可能的输入来找到对应的输出（穷举攻击），甚至可以通过字典或者适当的查找表加快查找速度。\n",
    "\n",
    "### 名词解释\n",
    "\n",
    "- realm（领域）：领域参数是**强制**的，在所有的盘问中都必须有。它是目的是鉴别SIP消息中的机密。在SIP实际应用中，它通常设置为SIP代理服务器所负责的域名\n",
    "\n",
    "  在要求用户输入用户名和口令时，SIP用户代理则会显示这个参数的内容给用户，以便用户使用正确的用户名和口令（这个服务器的）。\n",
    "\n",
    "- nonce ：这是由服务器规定的数据字符串，在服务器每次产生一个摘要盘问时，这个参数都是不一样的（与前面所产生的不会雷同）。“nonce”通常是由一些数据通过md5杂凑运算构造的。\n",
    "\n",
    "  这样的数据通常包括时间标识和服务器的机密短语。这确保每个“nonce”都有一个有限的生命期（也就是过了一些时间后会失效，并且以后再也不会使用），而且是独一无二的（即任何其它的服务器都不能产生一个相同的“nonce”）。\n",
    "  \n",
    "  客户端使用这个“nonce”来产生摘要响应（digest response），这样服务器也会在一个摘要响应中收到“nonce”的内容。服务器先要检查了“现时”的有效性后，才会检查摘要响应的其它部分。\n",
    " \n",
    "  因而，“nonce”在本质上是一种标识符，确保收到的摘要机密，是从某个特定的摘要盘问产生的。还限制了摘要盘问的生命期，防止未来的重播攻击。\n",
    "\n",
    "- opaque（不透明体）：这是一个不透明的（不让外人知道其意义）数据字符串，在盘问中发送给用户。\n",
    "\n",
    "  在摘要响应中，用户会将这个数据字符串发送回给服务器。这使得服务器可以是无状态的。如果需要在盘问和响应之间维护一些状态，可以用这个参数传送状态给客户端，此后当摘要响应回来时，再读这个状态。\n",
    " \n",
    "- algorithm（算法）：这是用来计算杂凑的算法。当前只支持MD5算法。\n",
    " \n",
    "- qop（保护的质量）。这个参数规定服务器支持哪种保护方案。客户端可以从列表中选择一个值。\n",
    "\n",
    "  “auth”表示只进行身份查验， “auth-int”表示进行查验外，还有一些完整性保护。需要看更详细的描述，请参阅RFC2617。\n",
    "\n",
    "- uri（统一资源指示符）：这个参数包含了客户端想要访问的URI。\n",
    "\n",
    "- nc：“nonce”计数器，这是一个16进制的数值，即客户端发送出请求的数量（包括当前这个请求），这些请求都使用了当前请求中这个“nonce”值。例如，对一个给定的“nonce”值，在响应的第一个请求中，客户端将发送“nc=00000001”。这个指示值的目的，是让服务器保持这个计数器的一个副本，以便检测重复的请求。如果这个相同的值看到了两次，则这个请求是重复的。\n",
    "\n",
    "- cnonce：这也是一个不透明的字符串值，由客户端提供，并且客户端和服务器都会使用，以避免用明文文本。这使得双方都可以查验对方的身份，并对消息的完整性提供一些保护。\n",
    "\n",
    "- response（响应）：这是由用户代理软件计算出的一个字符串，以证明用户知道口令。\n",
    "\n",
    "\n",
    "### 🔴示例及说明\n",
    "\n",
    "下面的例子仅仅涵盖了`auth`保护质量的代码，因为在撰写期间，所知道的只有Opera和Konqueror网页浏览器支持`auth-int`（带完整性保护的认证）。\n",
    "\n",
    "#### 典型的认证过程包括如下步骤\n",
    "\n",
    "1. 客户端请求一个需要认证的页面，但是不提供用户名和密码。通常这是由于用户简单的输入了一个地址或者在页面中点击了某个超链接。\n",
    "\n",
    "2. 服务器返回`401 \"Unauthorized\"` 响应，并且在返回的信息中包含了验证方式Digest，认证域(`realm`)，`QOP`(quality of protection)可能没有，以及一个**随机生成的密码随机数 `nonce`。在下面的请求中会一直使用到，当过了存活期后服务端将刷新生成一个新的nonce值；** \n",
    "\n",
    "3. 此时，浏览器会向用户提示认证域(`realm`)（通常是所访问的计算机或系统的描述），并且提示用户名和密码。用户此时可以选择取消。\n",
    "**一旦提供了用户名和密码，客户端会重新发送同样的请求，但是添加了一个认证头包括了响应代码`response`字段，这里的cnonce为客户端生成的nonce值，而nc用于统计，假设开始时为00000001，下次请求后就变成了00000002，不一定每次都加1，但是后面请求中的nc值肯定大于前一次请求中的nc值。**。\n",
    "  \n",
    "  注意：客户端可能已经拥有了用户名和密码，因此不需要提示用户，比如以前存储在浏览器里的。\n",
    "\n",
    "  response 字段根据qop的类型有不同的计算方法，详见上图。\n",
    "  \n",
    "  例如：QOP=\"auth\" 时，\n",
    " \n",
    "  HA1 = MD5(username:realm:password),\n",
    " \n",
    "  HA2 = MD5(method:uri)， \n",
    " \n",
    " response =MD5(HA1:nonce:nc:cnonce:qop:HA2)\n",
    "\n",
    "\n",
    "4. 服务端收到请求后将验证nonce是否过期，如果过期，那么直接返回401，即第二步的状态。如果没有过期，那么比较nc值，如果比前一次nc值小或者前一次根本没有存储的nc值，那么也将直接返回401状态。如果前面的验证都通过，那么服务端也将按照步骤3中计算最终HASH值的步骤计算出HASH值与客户端的进行比较，然后比较客户端提交过来的HASH值与服务端计算出来的HASH进行比较，不匹配返回401，匹配获取请求的数据并返回状态200。\n",
    "\n",
    "#### 1. 客户端请求 (无认证)：\n",
    "\n",
    "C -> S\n",
    "\n",
    "```http\n",
    "    GET /dir/index.html HTTP/1.0\n",
    "    Host: localhost\n",
    "    \\r\\n\n",
    "    (跟随一个新行，形式为一个回车再跟一个换行）\n",
    "```\n",
    "#### 2. 服务器响应\n",
    "\n",
    "S -> C\n",
    "\n",
    "注意WWW-Authenticate 字段：紧跟认证方式、`realm`、`qop`、`nonce`、`opaque`。\n",
    "\n",
    "```http\n",
    " HTTP/1.0 401 Unauthorized\n",
    "    Server: HTTPd/0.9\n",
    "    Date: Sun, 10 Apr 2005 20:26:47 GMT\n",
    "    WWW-Authenticate: Digest realm=\"testrealm@host.com\",   //认证域\n",
    "    qop=\"auth,auth-int\",   //保护质量\n",
    "    nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",  //服务器密码随机数\n",
    "    opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"\n",
    "    Content-Type: text/html\n",
    "    Content-Length: 311\n",
    "    \n",
    "    <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n",
    "     \"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\n",
    "    <HTML>\n",
    "      <HEAD>\n",
    "    <TITLE>Error</TITLE>\n",
    "    <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">\n",
    "      </HEAD>\n",
    "      <BODY><H1>401 Unauthorized.</H1></BODY>\n",
    "    </HTML>\n",
    "```\n",
    "\n",
    "#### 3. 客户端请求 (用户名 \"Mufasa\", 密码 \"Circle Of Life\")\n",
    "\n",
    "C -> S\n",
    "\n",
    "Authorization字段携带相同的`realm`、`opaque`、`nonce`， 额外附加 `username`、请求的`uri`、`qop`、`nc`、以及生成的`response`\n",
    "\n",
    "```http\n",
    "    GET /dir/index.html HTTP/1.0\n",
    "    Host: localhost\n",
    "    Authorization: Digest username=\"Mufasa\",\n",
    "     realm=\"testrealm@host.com\",\n",
    "     nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n",
    "     uri=\"/dir/index.html\",\n",
    "     qop=auth,\n",
    "     nc=00000001,//请求计数\n",
    "     cnonce=\"0a4f113b\",   //客户端密码随机数\n",
    "     response=\"6629fae49393a05397450978507c4ef1\",\n",
    "     opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"\n",
    "    (跟随一个新行，形式如前所述)。\n",
    "```\n",
    "\n",
    "#### 4. 服务器响应\n",
    "\n",
    "S -> C\n",
    "\n",
    "从客户端的Authorization字段以及已知的密码重新计算 response，与客户端的response字段进行对比，如果一致，则返回一个 `200 OK`的响应,附带请求的uri资源。\n",
    "\n",
    "```http\n",
    "    HTTP/1.0 200 OK\n",
    "    Server: HTTPd/0.9\n",
    "    Date: Sun, 10 Apr 2005 20:27:03 GMT\n",
    "    Content-Type: text/html\n",
    "    Content-Length: 7984\n",
    "    (随后是一个空行，然后是所请求受限制的HTML页面)\n",
    "```\n",
    "\n",
    "response 值由`三步计算`而成。当多个数值合并的时候，使用`冒号`作为分割符：\n",
    "\n",
    "1. 对用户名、认证域(realm)以及密码的合并值计算 MD5 哈希值，结果称为 HA1。\n",
    "\n",
    "2. 对HTTP方法以及URI的摘要的合并值计算 MD5 哈希值，例如，\"GET\" 和 \"/dir/index.html\"，结果称为 HA2。\n",
    "\n",
    "3. 对HA1、服务器密码随机数(nonce)、客户端请求计数(nc)、客户端密码随机数(cnonce)、保护质量(qop)以及 HA2 的合并值计算 MD5 哈希值。结果即为客户端提供的response 值。\n",
    "\n",
    "**因为服务器拥有与客户端同样的信息，因此服务器可以进行同样的计算，以验证客户端提交的 response 值的正确性。**在上面给出的例子中，结果是如下计算的。\n",
    "（MD5()表示用于计算MD5哈希值的函数；“”表示接下一行；引号并不参与计算）\n",
    "\n",
    "```java\n",
    "HA1 = MD5( \"Mufasa:testrealm@host.com:Circle Of Life\" )\n",
    "       = 939e7578ed9e3c518a452acee763bce9\n",
    "\n",
    "HA2 = MD5( \"GET:/dir/index.html\" )\n",
    "       = 39aff3a2bab6126f332b942af96d3366\n",
    "\n",
    "Response = MD5( \"939e7578ed9e3c518a452acee763bce9:00000001:0a4f113b:auth:39aff3a2bab6126f332b942af96d3366\" )\n",
    "= 6629fae49393a05397450978507c4ef1\n",
    "```\n",
    "\n",
    "此时客户端可以提交一个新的请求，**重复使用服务器密码随机数**(`nonce`)**（服务器仅在每次“401”响应后发行新的nonce），但是提供新的客户端密码随机数(cnonce)**。**在后续的请求中，十六进制请求计数器(nc)必须比前一次使用的时候要大**，否则攻击者可以简单的使用同样的认证信息重放老的请求。由服务器来确保在每个发出的密码随机数nonce时，计数器是在增加的，并拒绝掉任何错误的请求。显然，改变HTTP方法和/或计数器数值都会导致不同的 response值。\n",
    "\n",
    "**客户端在每次发起不同的请求方法时都需要计算response字段**，同样在服务器端校验时也默认采取同样的计算方法。\n",
    "\n",
    "服务器应当记住最近所生成的服务器密码随机数nonce的值。也可以在发行每一个密码随机数nonce后，记住过一段时间让它们过期。如果客户端使用了一个过期的值，服务器应该响应“401”状态号，并且在认证头中添加stale=TRUE，表明客户端应当使用新提供的服务器密码随机数nonce重发请求，而不必提示用户其它用户名和口令。\n",
    "\n",
    " **如果 Authorization 计算错误,Rtsp server 会一直响应 401**\n",
    "\n",
    "###  🔴java 实现  (jupyter[安装 java 内核](https://github.com/wanZzz6/Modules-Learn/blob/master/jupyter%20%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%90%AD%E5%BB%BA.ipynb)可直接执行） \n",
    "\n",
    "qop = \"auth\"情况下"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import java.io.UnsupportedEncodingException;\n",
    "import java.security.MessageDigest;\n",
    "import java.security.NoSuchAlgorithmException;\n",
    "\n",
    "class Test {\n",
    "    static String userName = \"Mufasa\";\n",
    "    static String password = \"Circle Of Life\";\n",
    "    static String realm = \"testrealm@host.com\";\n",
    "    static String mediaUrl = \"/dir/index.html\";\n",
    "    static String nonce = \"dcd98b7102dd2f0e8b11d0f600bfb0c093\";\n",
    "    static String opaque = \"5ccc069c403ebaf9f0171e9517f40e41\";\n",
    "    static String nc = \"00000001\";\n",
    "    static String cnonce = \"0a4f113b\";\n",
    "    static String qop = \"auth\";\n",
    "    static String method = \"GET\";\n",
    "\n",
    "    // qop = auth\n",
    "    public static String getAuthorization_auth() {\n",
    "        \n",
    "        String HA1 = computeMd5Hash(userName + \":\" + realm + \":\" + password);\n",
    "        System.out.println(\"HA1--\" + HA1);\n",
    "\n",
    "        String HA2 = computeMd5Hash(method + \":\" + mediaUrl);\n",
    "        System.out.println(\"HA2--\" + HA2);\n",
    "\n",
    "        String response = computeMd5Hash(HA1 + \":\"  + nonce + \":\" + nc + \":\" + cnonce + \":\" + qop + \":\" + HA2);\n",
    "        System.out.println(\"Response--\" + response);\n",
    "\n",
    "        String mAuthorization = \"Digest username=\\\"\" + userName + \"\\\", realm=\\\"\" + realm + \"\\\", nonce=\\\"\" + nonce\n",
    "                + \"\\\", uri=\\\"\" + mediaUrl +\"\\\", qop=\" + qop + \", nc=\" + nc + \", cnonce=\\\"\" + cnonce + \"\\\", response=\\\"\" \n",
    "                + response + \"\\\"\" + \"\\\", opaque=\\\"\" + opaque;\n",
    "        return mAuthorization;\n",
    "    }\n",
    "    \n",
    "    //qop 未指定\n",
    "    public static String getAuthorization() {\n",
    "        String HA1 = computeMd5Hash(userName + \":\" + realm + \":\" + password);\n",
    "        System.out.println(\"HA1--\" + HA1);\n",
    "\n",
    "        String HA2 = computeMd5Hash(method + \":\" + mediaUrl);\n",
    "        System.out.println(\"HA2--\" + HA2);\n",
    "\n",
    "        String response = computeMd5Hash(HA1 + \":\" + nonce + \":\" + HA2);\n",
    "        System.out.println(\"Response--\" + response);\n",
    "\n",
    "        String mAuthorization = \"Digest username=\\\"\" + userName + \"\\\", realm=\\\"\" + realm + \"\\\", nonce=\\\"\" + nonce\n",
    "                + \"\\\", uri=\\\"\" + mediaUrl + \"\\\", response=\\\"\" + response + \"\\\"\";\n",
    "        return mAuthorization;\n",
    "    }\n",
    "\n",
    "    // computeMd5Hash 为 md5 方法\n",
    "    public static String computeMd5Hash(String buffer) {\n",
    "        MessageDigest md;\n",
    "        try {\n",
    "            md = MessageDigest.getInstance(\"MD5\");\n",
    "            return bytesToHex(md.digest(buffer.getBytes(\"UTF-8\")));\n",
    "        } catch (NoSuchAlgorithmException ignore) {\n",
    "        } catch (UnsupportedEncodingException e) {\n",
    "        }\n",
    "        return \"\";\n",
    "    }\n",
    "\n",
    "    private static String bytesToHex(byte[] bytes) {\n",
    "        char[] hexChars = new char[bytes.length * 2];\n",
    "        int v;\n",
    "        for (int j = 0; j < bytes.length; j++) {\n",
    "            v = bytes[j] & 0xFF;\n",
    "            hexChars[j * 2] = hexArray[v >>> 4];\n",
    "            hexChars[j * 2 + 1] = hexArray[v & 0x0F];\n",
    "        }\n",
    "        return new String(hexChars);\n",
    "    }\n",
    "    \n",
    "    final protected static char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',\n",
    "            'e', 'f' };\n",
    "}\n",
    "\n",
    "Test t = new Test();\n",
    "System.out.println(t.getAuthorization_auth());\n",
    "System.out.println(\"---------------------------------\");\n",
    "System.out.println(t.getAuthorization())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 🔴Python 实现\n",
    "\n",
    "qop = \"auth\"情况下"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hashlib import md5\n",
    "\n",
    "userName = \"Mufasa\"\n",
    "password = \"Circle Of Life\"\n",
    "mediaUrl = \"/dir/index.html\";\n",
    "realm = \"testrealm@host.com\"\n",
    "nonce = \"dcd98b7102dd2f0e8b11d0f600bfb0c093\"\n",
    "nc = \"00000001\"\n",
    "cnonce = \"0a4f113b\"\n",
    "qop = \"auth\"\n",
    "method = 'GET'\n",
    "\n",
    "HA1 = md5((userName + \":\" + realm + \":\" + password).encode()).hexdigest()\n",
    "print('HA1--', HA1)\n",
    "\n",
    "HA2 = md5((method + \":\" + uri).encode()).hexdigest()\n",
    "print('HA2--', HA2)\n",
    "\n",
    "response = md5((HA1 + \":\"  + nonce + \":\" + nc + \":\" + cnonce + \":\" + qop + \":\" + HA2).encode()).hexdigest()\n",
    "print('Response--', response)\n",
    "\n",
    "mAuthorization = \"Digest username=\\\"\" + userName + \"\\\", realm=\\\"\" + realm + \"\\\", nonce=\\\"\" + nonce\\\n",
    "                + \"\\\", uri=\\\"\" + mediaUrl + \"\\\", response=\\\"\" + response + \"\\\"\";\n",
    "print(mAuthorization)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cookie Auth\n",
    "\n",
    "**Cookie认证机制**：用户输入用户名和密码发起请求，服务器认证后给每个Session分配一个唯一的`JSESSIONID`，并通过Cookie发送给客户端。\n",
    "\n",
    "当客户端发起新的请求的时候，将在Cookie头中携带这个`JSESSIONID`。这样服务器能够找到这个客户端对应的Session。默认的，当我们关闭浏览器的时候，客户端cookie会被删除，可以通过修改cookie 的`expire time`使cookie在一定时间内有效。但是服务器端的session不会被销毁，除非通过invalidate或超时。\n",
    "\n",
    "## Token Auth\n",
    "\n",
    "![](..\\assets\\tokenAuth.png)\n",
    "\n",
    "### 常用的Token Auth（和Cookie Auth区别不大）：\n",
    "\n",
    "1. 首次登陆，用户名和密码验证过之后，将sessionId保存在token中，或者将一个key保存在token中，key的值可以设置为用户唯一性的信息(账号/密码/身份认证机制(电话号/身份证号/支付宝账号/银行卡信息)...)；当然我们在程序中的实现是保存UUID作为ticket。\n",
    "\n",
    "2. 设置token的有效期，并保存在服务器数据库中；\n",
    "\n",
    "3. 服务器将这个token值返回给客户端，客户端拿到 token 值之后，将 token 保存在 cookie 中，以后客户端再次发送网络请求(一般不是登录请求)的时候,就会将这个 token 值附带到参数中发送给服务器。服务器接收到客户端的请求之后,会取出token值与保存在本地(数据库)中的token值做对比!\n",
    "\n",
    "    如果两个 token 值相同 :说明用户登录成功过!当前用户处于登录状态!如果没有这个token或者过期，则设置token为无效，并让用户重新登录。\n",
    "\n",
    "这种方式在客户端变化不大，也要利用cookie，改动的是服务器端\n",
    "\n",
    "过去：通过sessionId查找Tomcat服务器内存中是否有sessionId对应的session存在，若存在，则表示登陆过，然后从session找出用户信息；\n",
    "\n",
    "现在：通过token查找数据库中是否有相同的token，并且token要处于有效期前，有的话通过token在数据库中找出用户信息，否则重新登录，(其实还包括sessionId的验证，因为jsp默认创建session)。\n",
    "如果觉得查询数据库比较耗时，可以用memcache或者redis缓存一下。\n",
    "\n",
    "**首先说明一下session何时会被创建：**\n",
    "\n",
    "1、 请求JSP页面时自动创建session，利用`request.getSession(true)`；语句\n",
    "\n",
    "原因:\n",
    "\n",
    "由于**HTTP是无状态**协议，这意味着每次客户端检索网页时，都要单独打开一个服务器http连接，如果我同一个浏览器，不同页面打开你的主页10次，那就要进行10次连接和断开(TCP3次握手，4次挥手)，浪费系统资源，http提供了一种长连接，`keep-alive`，相同会话的不同请求可以用同一连接，**故jsp默认创建session**。而session的创建过程中会自动将**sessionId**写入cookie的`JSESSIONID`中的，这样，只要不关闭浏览器，你在同一网站的任意网页跳转，由于每次请求都会携带同一个sessionId，不会重新创建新的会话，防止创建多个会话浪费系统资源。\n",
    "\n",
    "否则：黑客利用几台主机，疯狂的点击某一个JSP页面，如果每次点击都创建一个新的会话，可能使服务器崩溃。\n",
    "\n",
    "例子：\n",
    "登录函数：\n",
    "\n",
    "```java\n",
    "// 用户登录操作\n",
    "public void login(HttpServletRequest request,\n",
    "        HttpServletResponse response) throws ServletException, IOException {\n",
    "    request.setCharacterEncoding(\"gb2312\");\n",
    "    String account = request.getParameter(\"account\");\n",
    "    consumerDao = new ConsumerDao();\n",
    "    ConsumerForm consumerForm = consumerDao.getConsumerForm(account);\n",
    "    if (consumerForm == null) {\n",
    "        request.setAttribute(\"information\", \"您输入的用户名不存在，请重新输入！\");\n",
    "    } else if (!consumerForm.getPassword().equals(request.getParameter(\"password\"))) {\n",
    "        request.setAttribute(\"information\", \"您输入的登录密码有误，请重新输入！\");\n",
    "    } else {\n",
    "\n",
    "        request.setAttribute(\"form\", consumerForm);\n",
    "    }\n",
    "    RequestDispatcher requestDispatcher = request.getRequestDispatcher(\"dealwith.jsp\");\n",
    "    requestDispatcher.forward(request, response);\n",
    "}\n",
    "```\n",
    "\n",
    "在登录界面，浏览器`F12`打开开发人员选项 -> Application -> Cookies\n",
    "\n",
    "可见有一个`JSESSIONID`，并且点击其他页面并没有开启新的`JSESSIONID`。但是换一个浏览器就会产生新的`JSESSIONID`，因为不同浏览器的会话缓存是不可以互相用的登录以后还是这个`JSESSIONID`.\n",
    "\n",
    "**Session的销毁只有三种方式：**\n",
    "1. 调用了session.invalidate()方法\n",
    "2. session过期（超时）\n",
    "3. 服务器重新启动\n",
    "\n",
    "单个JSP页面禁用session方式\n",
    "\n",
    "```java\n",
    "<%@ page session=\"false\">\n",
    "```\n",
    "\n",
    "   在servlet中，只要代码不调用Session相关的API就不会创建session\n",
    "    `request.getSession()` 等价于 `request.getSession(true)`\n",
    "    \n",
    "   这两个方法的作用是相同的，查找请求中是否有关联的JSESSIONID，如果有则返回这个号码所对应的session对象，如果没有则生成一个新的session对象。所以说，通过此方法是一定可以获得一个session对象。\n",
    "    \n",
    "   request.getSession(false) 查找请求中是否有关联的JSESSIONID号，如果有则返回这个号码所对应的session对象，如果没有则返回一个null。\n",
    "    注意在创建session的过程中，sessionId是自动写入cookie的JSESSIONID中的，如果禁用cookie，则通过url回传.\n",
    "    \n",
    "#### 真正的应用：JWT\n",
    "\n",
    "通过token可以将用户的基本信息(非隐私的，比如UserId，过期时间，生成的随机key等)全部加密签名后放入token中，从而服务器端不需要保存用户登录信息，大大减轻服务器压力。用户认证完全靠token识别，通过签名来保证token没有被修改过（只有服务器才知道秘钥，比如常见的非对称加密算法），是服务器下发的token。在后续请求中，服务端只需要对用户请求中包含的token进行解码，验证用户登录是否过期。\n",
    "\n",
    "很多大型网站也都在用，比如 Facebook，Twitter，Google+，Github 等等，比起传统的身份验证方法，Token 扩展性更强，也更安全点，非常适合用在 Web 应用或者移动应用上。\n",
    "\n",
    "**Token Auth优点：**\n",
    "\n",
    "- 减轻服务器压力：通过token可以将用户的基本信息(非隐私的，比如UserId，过期时间，生成的随机key等)全部加密签名后放入token中，从而服务器端不需要保存用户登录信息，大大减轻服务器压力。用户认证完全靠token识别，通过签名来保证token没有被修改过（只有服务器才知道秘钥，比如常见的非对称加密算法），是服务器下发的token。\n",
    "- 支持跨域访问：因为服务器并没有保存登录状态，完全靠签名的token识别，那么另一个网站只要有对应的私钥，就可以对token验证，前提是传输的用户认证信息通过HTTP头传输；\n",
    "- 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料（如：javascript，HTML,图片等），因为不需要同步服务器上的登录状态信息；\n",
    "- 性能更好: 因为从token中可以获得userId，不用查询登录状态表；\n",
    "\n",
    "[详细](http://www.cnblogs.com/xiekeli/p/5607107.html)\n",
    "\n",
    "**但是都不能很好的预防会话劫持**\n",
    "\n",
    "\n",
    "## 什么是Realm\n",
    "\n",
    "Tomcat提供Realm支持。\n",
    "\n",
    "**Tomcat使用Realm使某些特定的用户组具有访问某些特定的Web应用的权限，而没有权限的用户不能访问这个应用。**\n",
    "\n",
    "Tomcat提供了三种不同Realm对访问某个Web应用程序的用户进行相应的验证。\n",
    "\n",
    "1. JDBCRealm，这个Realm将用户信息存在*数据库*里，通过JDBC从数据库中获得用户信息并进行验证。\n",
    "2. JNDIRealm，将用户信息存在基于LDAP等*目录服务的服务器*里，通过JNDI技术从LDAP服务器中获取用户信息并进行验证。\n",
    "3. MemoryRealm，将用户信息存在一个*xml文件*中，对用户进行验证时，将会从相应的xml文件中提取用户信息。manager(Tomcat提供的一个web应用程序)应用在进行验证时即使用此种Realm。Realm类似于Unix里面的group。在Unix中，一个group对应着系统的一定资源，某个group不能访问不属于它的资源。Tomcat用Realm来对不同的应用(类似系统资源)赋给不同的用户(类似group)。没有权限的用户则不能访问这个应用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# HTTPS传输协议原理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  },
  "nbTranslate": {
   "displayLangs": [
    "*"
   ],
   "hotkey": "alt-t",
   "langInMainMenu": true,
   "sourceLang": "en",
   "targetLang": "fr",
   "useGoogleTranslate": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "270.406px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
